/**
 * 版权所有(C)，上海海鼎信息工程股份有限公司，2008，所有权利保留。
 * 
 * 项目名：	rumba
 * 文件名：	ModuleLoader.java
 * 模块说明：	
 * 修改历史：
 * Feb 4, 2008 - lxm - 创建。
 */
package com.hd123.rumba.gwt.cpnts.module.client;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Frame;
import com.gwtext.client.widgets.MessageBox;
import com.hd123.rumba.gwt.base.client.exception.ClientBizException;
import com.hd123.rumba.gwt.base.client.util.GWTUtil;
import com.hd123.rumba.gwt.base.client.util.ObjectUtil;
import com.hd123.rumba.gwt.base.client.util.Mutex;

/**
 * 模块导航器，提供页面导航到指定页面或gwt模块的功能。
 * <p>
 * 
 * 如果指定的是一个gwt模块，包含了一个模块运行时替换的策略，这个策略是通过rpc方式由服务器完成。一般情况下通过调用静态方法
 * {@link #navigate(String, String, int)}方法即可。
 * <p>
 * 
 * 另外，还提供了一种更为复杂的调用方式，设计用于一些可能会包含了多个模块导航的gwt模块，以避免过多的rpc调用。具体说其中包含了一个缓冲，调用者事先将
 * 所有可能调用的连接地址放入其中，然后通过调用{@link #prepare()}方法，进行批量的rpc调用（一次调用）。
 * 
 * @author lxm
 * 
 */
public class ModuleNavigator {

  /** 链接类型取值：简单类型，表示url不需要被修饰即可直接使用。 */
  public static final int VALUE_STYLE_SIMPLE = 0;
  /** 链接类型取值：Action类型，表示url将会被一个Action解释。 */
  public static final int VALUE_STYLE_ACTION = 1;
  /** 链接类型取值：gwt模块类型，表示url指向一个gwt模块。 */
  public static final int VALUE_STYLE_GWTMODULE = 2;

  private static final int MINVALUE_STYLE = VALUE_STYLE_SIMPLE;
  private static final int MAXVALUE_STYLE = VALUE_STYLE_GWTMODULE;
  private static final int TIMEOUT_RPC = 5000;

  /** @see #navigate(String, String, int) */
  public static void navigate(String url) throws ClientBizException {
    navigate(url, null, VALUE_STYLE_GWTMODULE);
  }

  /** @see #navigate(String, String, int) */
  public static void navigate(String url, String target) throws ClientBizException {
    navigate(url, target, VALUE_STYLE_GWTMODULE);
  }

  /** @see #navigate(String, String, int) */
  public static void navigate(String url, int style) throws ClientBizException {
    navigate(url, null, style);
  }

  private static String rpcTarget = null;
  private static final String MUTEX_ID = "com.hd123.rumba.gwt.cpnts.module.ModuleService";

  /**
   * 导航到一个指定的url。
   * <p>
   * 若指定的url是一个gwt模块，将会导致一个异步的rpc调用。
   * 
   * @param url
   *            指定的url。
   * @param target
   *            指定打开链接的窗口target。允许传入null，表示当前窗口。
   * @param style
   *            指定url的类型，取值参见常量“VALUE_STYLE_*”，默认为{@link #VALUE_STYLE_GWTMODULE}。
   * @throws ClientBizException
   */
  public static void navigate(String url, String target, int style) throws ClientBizException {
    ObjectUtil.checkParameterNotNull(url, "url");
    checkStyle(style);

    if (style != VALUE_STYLE_GWTMODULE)
      doNavigate(url, target);

    rpcTarget = target;
    rc_url = url;
    Mutex.waitFor(MUTEX_ID, TIMEOUT_RPC, new Mutex.Callback() {
      public void onSuccess() {
        rc_navigate();
      }
    });
  }

  private static String rc_url = null;

  static void rc_navigate() {
    ModuleService.Locator.getService().getRealModuleURL(rc_url, new AsyncCallback() {

      public void onFailure(Throwable caught) {
        Mutex.release(MUTEX_ID);
        MessageBox.alert(Window.getTitle(), "取得gwt模块链接地址时发生错误。\n" + caught.getLocalizedMessage());
      }

      public void onSuccess(Object result) {
        Mutex.release(MUTEX_ID);
        String url = GWTUtil.getWebAppBaseURL() + (String) result;
        doNavigate(url, rpcTarget);
      }

    });
  }

  /** 缓冲区，包含了url与style的映射关系。 */
  private HashMap styleMap = new HashMap();
  /** 缓冲区，包含了url与真实地址的映射关系。 */
  private HashMap realURLMap = new HashMap();

  /**
   * 清除缓冲器。
   * 
   */
  public void clear() {
    this.styleMap = new HashMap();
    this.realURLMap = new HashMap();
  }

  /** @see #add(String, int) */
  public void add(String url) throws ClientBizException {
    this.add(url, VALUE_STYLE_GWTMODULE);
  }

  /**
   * 添加一个链接到缓冲器
   * 
   * @param url
   *            指定被添加的url。
   * @param style
   *            指定url的类型，取值参见常量“VALUE_STYLE_*”，默认为{@link #VALUE_STYLE_GWTMODULE}。
   * @throws ClientBizException
   */
  public void add(String url, int style) throws ClientBizException {
    ObjectUtil.checkParameterNotNull(url, "url");
    checkStyle(style);

    this.styleMap.put(url, new Integer(style));
    if (style == VALUE_STYLE_GWTMODULE)
      this.realURLMap.remove(url);
    else if (style == VALUE_STYLE_ACTION)
      if (url.startsWith("http://"))
        this.realURLMap.put(url, url);
      else
        this.realURLMap.put(url, GWTUtil.getWebAppBaseURL() + url);
    else
      this.realURLMap.put(url, url);
  }

  /**
   * 从缓冲器中删除指定链接的有关信息。
   * 
   * @param url
   *            指定被删除的url。
   * @throws ClientBizException
   */
  public void remove(String url) throws ClientBizException {
    ObjectUtil.checkParameterNotNull(url, "url");

    this.styleMap.remove(url);
    this.realURLMap.remove(url);
  }

  /**
   * 重置缓冲器中已经通过方法{@link #prepare()}取得的真实地址的信息。
   * 
   */
  public void reset() {
    this.realURLMap.clear();
    Object[] urls = this.styleMap.keySet().toArray();
    for (int i = 0; i < urls.length; i++) {
      String url = (String) urls[i];
      Integer style = (Integer) this.styleMap.get(url);
      assert style != null;
      if (style.intValue() != VALUE_STYLE_GWTMODULE)
        this.realURLMap.put(url, url);
    }
  }

  /**
   * 检查缓冲器中所有未取得真实地址的，通过rpc调用取得，并写入缓冲。
   * 
   */
  public void prepare() {
    try {
      this.doPrepare(null, null);
    } catch (ClientBizException e) {
    }
  }

  /**
   * 从缓冲中读取指定url对应的真实地址。
   * 
   * @param url
   *            指定的url。
   * @return 可能返回null，有两种可能：一种是由于该url尚未被加入到缓冲中；另一种则是已经被加入到缓冲，但尚未调用
   *         {@link #prepare()}。
   * @throws ClientBizException
   */
  public String getRealUrl(String url) throws ClientBizException {
    ObjectUtil.checkParameterNotNull(url, "url");
    return (String) this.realURLMap.get(url);
  }

  /** @see #navigate2(String, Frame, int) */
  public void navigate2(String url) throws ClientBizException {
    ObjectUtil.checkParameterNotNull(url, "url");
    checkStyle(VALUE_STYLE_GWTMODULE);

    if (this.getRealUrl(url) == null)
      this.add(url, VALUE_STYLE_GWTMODULE);
    this.doPrepare(url, null);
  }

  /** @see #navigate2(String, String, int) */
  public void navigate2(String url, String target) throws ClientBizException {
    this.navigate2(url, target, VALUE_STYLE_GWTMODULE);
  }

  /** @see #navigate2(String, Frame, int) */
  public void navigate2(String url, Frame target) throws ClientBizException {
    this.navigate2(url, target, VALUE_STYLE_GWTMODULE);
  }

  /** @see #navigate2(String, String, int) */
  public void navigate2(String url, int style) throws ClientBizException {
    ObjectUtil.checkParameterNotNull(url, "url");
    checkStyle(style);

    if (this.getRealUrl(url) == null)
      this.add(url, style);
    this.doPrepare(url, null);
  }

  /**
   * 导航到一个指定的url。
   * <p>
   * 将会首先检查指定的url信息是否已经被包含在缓冲中，若没有包含则将被加入缓冲，并将调用{@link #prepare()}。
   * 
   * @param url
   *            指定的url。
   * @param target
   *            指定打开链接的窗口target。允许传入null，表示当前窗口。
   * @param style
   *            指定url的类型，取值参见常量“VALUE_STYLE_*”。若不指定将首先查找缓冲区中是否已经指定，若缓冲区中没有则默认为{@link #VALUE_STYLE_GWTMODULE}。
   * @throws ClientBizException
   */
  public void navigate2(String url, String target, int style) throws ClientBizException {
    ObjectUtil.checkParameterNotNull(url, "url");
    checkStyle(style);

    if (this.getRealUrl(url) == null)
      this.add(url, style);
    this.doPrepare(url, target);
  }

  /**
   * 导航到一个指定的url。
   * <p>
   * 将会首先检查指定的url信息是否已经被包含在缓冲中，若没有包含则将被加入缓冲，并将调用{@link #prepare()}。
   * 
   * @param url
   *            指定的url。
   * @param target
   *            指定打开链接的窗口Frame。允许传入null，表示当前窗口。
   * @param style
   *            指定url的类型，取值参见常量“VALUE_STYLE_*”。若不指定将首先查找缓冲区中是否已经指定，若缓冲区中没有则默认为{@link #VALUE_STYLE_GWTMODULE}。
   * @throws ClientBizException
   */
  public void navigate2(String url, Frame target, int style) throws ClientBizException {
    ObjectUtil.checkParameterNotNull(url, "url");
    checkStyle(style);

    if (this.getRealUrl(url) == null)
      this.add(url, style);
    this.doPrepare(url, target);
  }

  private static void checkStyle(int style) throws ClientBizException {
    if (style < MINVALUE_STYLE || style > MAXVALUE_STYLE)
      throw new ClientBizException("指定的参数style必须为" + MINVALUE_STYLE + "与" + MAXVALUE_STYLE
          + "之间的整数(" + style + ")。");
  }

  private static void doNavigate(String url, Object target) {
    if (target == null)
      GWTUtil.navigate(url);
    else if (target instanceof Frame)
      ((Frame) target).setUrl(url);
    else
      GWTUtil.navigate(url, (String) target);
  }

  private String rpcNavigateURL = null;
  private Object rpcNavigateTarget = null;

  private void doPrepare(String navigateURL, Object target) throws ClientBizException {
    rc_relaURLs = new HashSet();
    Object[] urls = this.styleMap.keySet().toArray();
    for (int i = 0; i < urls.length; i++) {
      String url = (String) urls[i];
      Integer style = (Integer) this.styleMap.get(url);
      assert style != null;
      if (!this.realURLMap.containsKey(url)) {
        if (style.intValue() == VALUE_STYLE_GWTMODULE)
          rc_relaURLs.add(url);
        else
          this.realURLMap.put(url, url);
      }
    }

    rpcNavigateURL = null;
    rpcNavigateTarget = null;
    if (navigateURL != null) {
      // 判断如有必要就立即调用导航，否则滞后导航。
      String realURL = this.getRealUrl(navigateURL);
      if (realURL == null) {
        rpcNavigateURL = navigateURL;
        rpcNavigateTarget = target;
      } else
        doNavigate(realURL, target);
    }

    if (rc_relaURLs.size() == 0)
      return;

    Mutex.waitFor(MUTEX_ID, TIMEOUT_RPC, new Mutex.Callback() {
      public void onSuccess() {
        rc_doPrepare();
      }
    });
  }

  private Set rc_relaURLs = null;

  void rc_doPrepare() {
    ModuleService.Locator.getService().getRealModuleURLs(rc_relaURLs, new AsyncCallback() {
      public void onFailure(Throwable caught) {
        Mutex.release(MUTEX_ID);
        MessageBox.alert(Window.getTitle(), "取得gwt模块链接地址时发生错误。\n" + caught.getLocalizedMessage());
      }

      public void onSuccess(Object result) {
        Mutex.release(MUTEX_ID);
        Map map = (Map) result;
        Object[] relaURLs = map.keySet().toArray();
        for (int i = 0; i < relaURLs.length; i++) {
          String relaURL = (String) relaURLs[i];
          String realURL = GWTUtil.getWebAppBaseURL() + (String) map.get(relaURL);
          realURLMap.put(relaURL, realURL);
        }

        // 滞后导航
        if (rpcNavigateURL != null)
          try {
            String url = getRealUrl(rpcNavigateURL);
            doNavigate(url, rpcNavigateTarget);
          } catch (ClientBizException e) {
            MessageBox.alert(Window.getTitle(), "无法导航到指定地址：" + rpcNavigateURL + "\n"
                + e.getMessage());
          }
      }
    });
  }

}
